-module(xmpp_clients).

-include_lib("exmpp.hrl").
-include_lib("exmpp_client.hrl").

-export([
	start/0,
	stop/1,
	open_connection/2,
	connections_registrar_loop/0,
	open_connection_with_monitor/2
	]).

-define(CONNECT_NEXT_JID_DELAY_MS, 10).

-define(TIMEOUT_RECONNECT_DELAY_MIN, 5  * 60 * 1000).
-define(TIMEOUT_RECONNECT_DELAY_MAX, 15 * 60 * 1000).

-define(SESSION_MIN_LIFETIME, 10 * 60 * 1000).
-define(SESSION_MAX_LIFETIME, 20 * 60 * 1000).

start() ->
    application:start(crypto),
    application:start(public_key),
    application:start(ssl),
    application:start(exmpp),
    register(connections_registrar_pid, spawn(?MODULE, connections_registrar_loop, [])),
	process_guids_from_stdin().

open_connection_with_monitor(JID, Pass) ->
	Pid = spawn(?MODULE, open_connection, [JID, Pass]),
	erlang:monitor(process, Pid),
	receive
		{'DOWN', Ref, process, Pid, Record } ->
			open_connection_with_monitor(JID, Pass);
		Record ->
			io:format("Unknown record ~p~n", [ Record ]),
			open_connection_with_monitor(JID, Pass)
% 			conn_processes_monitor(JID, Pass)
% 		{'DOWN', Ref, process, Pid2, Reason} ->

	end.
	
process_guids_from_stdin() ->
	case io:get_line(standard_io, "") of
		eof  ->
			io:format("end-of-input-file~n");
		Line ->
			JID_Pass_Pair = string:strip(string:strip(Line, both, 13), both, 10),
			[JID, Pass]   = string:tokens(JID_Pass_Pair, " "),

			spawn(?MODULE, open_connection_with_monitor, [JID, Pass]),
			timer:sleep(?CONNECT_NEXT_JID_DELAY_MS),
			process_guids_from_stdin()
	end.

get_time() ->
	{A1,A2,A3} = now(),
	A1 * 1000000 + A2.

connections_registrar_loop() ->
	connections_registrar_loop(0, 0, get_time()).

connections_registrar_loop(NumConnected, NumConnectedRecent, PrevTime) ->
	CurrentTime = get_time(),
	if
		CurrentTime - PrevTime > 10 ->
			io:format("Number of connected users: ~w ~w\n", [ NumConnected, NumConnectedRecent / (CurrentTime - PrevTime) ]),
			connections_registrar_loop(NumConnected, 0, CurrentTime);
		true ->
			receive
				stop ->
					io:format("Stop registrar loop");
				connected ->
					connections_registrar_loop(NumConnected + 1, NumConnectedRecent + 1, PrevTime);
				disconnected ->
					connections_registrar_loop(NumConnected - 1, NumConnectedRecent, PrevTime)
			after 10000 ->
				connections_registrar_loop(NumConnected, NumConnectedRecent, PrevTime)
			end
	end.

stop(EchoClientPid) ->
    EchoClientPid ! stop.
    
open_connection(JID, Password) ->
	random:seed(now()),
%%    io:format("application started~n", []),
    %% Start XMPP session: Needed to start service (Like
    %% exmpp_stringprep):
    MySession = exmpp_session:start(),
%    io:format("session started~n", []),
    %% Create XMPP ID (Session Key):
    [User, Server] = string:tokens(JID, "@"),
    MyJID = exmpp_jid:make(User, Server, random),
    %% Create a new session with basic (digest) authentication:
    exmpp_session:auth_basic_digest(MySession, MyJID, Password),
    %% Connect in standard TCP:
    %% {ok, _StreamId} = exmpp_session:connect_TCP(MySession, Server, 5222),
    try
		ConnectOptions = [{whitespace_ping, 300}], % , {timeout, 60000}],
		case exmpp_session:connect_SSL(MySession, Server, 5223, ConnectOptions) of
			{ok, _StreamId} ->
	%    		io:format("connected~n", []),
				session(MySession, MyJID, Password, JID);
			_ -> io:format("error occured~n", [])
		end
	catch _ ->
% 		io:format("couldn't connect ~p~n", [ JID ]),
		timer:sleep(?TIMEOUT_RECONNECT_DELAY_MIN + random:uniform(?TIMEOUT_RECONNECT_DELAY_MAX - ?TIMEOUT_RECONNECT_DELAY_MIN)),
		open_connection(JID, Password)
	end.

%% We are connected. We now log in (and try registering if authentication fails)
session(MySession, _MyJID, Password, SourceJID) ->
    %% Login with defined JID / Authentication:
%    io:format("logging in...~n", []),
    exmpp_session:login(MySession),
%     catch
% 	throw:{auth_error, 'not-authorized'} ->
	    %% Try creating a new user:
% 	    io:format("Register~n",[]),
	    %% In a real life client, we should trap error case here
	    %% and print the correct message.
% 	    exmpp_session:register_account(MySession, Password),
	    %% After registration, retry to login:
% 	    exmpp_session:login(MySession);
% 	_ ->
% 	    io:format("login exception~n",[])
%     end,
    connections_registrar_pid ! connected,
    %% We explicitely send presence:
    exmpp_session:send_packet(MySession,
			      exmpp_presence:set_status(
				exmpp_presence:available(), "Echo Ready")),
	loop(MySession, SourceJID, Password).

%% Process exmpp packet:
loop(MySession, SourceJID, Password) ->
	Timeout = ?SESSION_MIN_LIFETIME + random:uniform(?SESSION_MAX_LIFETIME - ?SESSION_MIN_LIFETIME),  % 5000 + random:uniform(10000),
%	io:format("TImeout: ~w\n", [ Timeout ]),
	receive
		reconnect ->
			connections_registrar_pid ! disconnected,
			exmpp_session:stop(MySession),
			open_connection(SourceJID, Password);
		stop ->
			connections_registrar_pid ! disconnected,
			exmpp_session:stop(MySession);
		{stream_error, Message} ->
			io:format("stream_error received ~p~n", [ Message ]),
			connections_registrar_pid ! disconnected,
			open_connection(SourceJID, Password);
% 			self() ! reconnect;
% 			connections_registrar_pid ! { stream_error, SourceJID, Password },
% 			exmpp_session:stop(MySession);

		%% If we receive a message, we reply with the same message
		Record = #received_packet{packet_type=message,
				raw_packet=Packet,
				type_attr=Type} when Type =/= "error" ->
%%            io:format("Received Message stanza:~n~p~n~n", [Record]),
			echo_packet(MySession, Packet),
			loop(MySession, SourceJID, Password);
		%% If we receive a presence stanza, handle it
		Record when Record#received_packet.packet_type == 'presence' ->
	%%	    io:format("Received Presence stanza:~n~p~n~n", [Record]),
			handle_presence(MySession, Record, Record#received_packet.raw_packet),
			loop(MySession, SourceJID, Password);
		Record ->
			io:format("Received a stanza:~n~p~n~n", [Record]),
			loop(MySession, SourceJID, Password)
% 		true ->
% 			io:format("unhandled message~n")
	after Timeout ->
		self() ! reconnect,
		loop(MySession, SourceJID, Password)
	end.

%% Send the same packet back for each message received
echo_packet(MySession, Packet) ->
    From = exmpp_xml:get_attribute(Packet, <<"from">>, <<"unknown">>),
    To = exmpp_xml:get_attribute(Packet, <<"to">>, <<"unknown">>),
    TmpPacket = exmpp_xml:set_attribute(Packet, <<"from">>, To),
    TmpPacket2 = exmpp_xml:set_attribute(TmpPacket, <<"to">>, From),
    NewPacket = exmpp_xml:remove_attribute(TmpPacket2, <<"id">>),
    exmpp_session:send_packet(MySession, NewPacket).

handle_presence(Session, Packet, _Presence) ->
    case exmpp_jid:make(_From = Packet#received_packet.from) of
	JID ->
	    case _Type = Packet#received_packet.type_attr of
		"available" ->
		    %% handle presence availabl
		    ok;
		"unavailable" ->
		    %% handle presence unavailable
		    ok;
		"subscribe" ->
		    presence_subscribed(Session, JID),
		    presence_subscribe(Session, JID);
		"subscribed" ->
		    presence_subscribed(Session, JID),
		    presence_subscribe(Session, JID)
	    end
    end.

presence_subscribed(Session, Recipient) ->
    Presence_Subscribed = exmpp_presence:subscribed(),
    Presence = exmpp_stanza:set_recipient(Presence_Subscribed, Recipient),
    exmpp_session:send_packet(Session, Presence).

presence_subscribe(Session, Recipient) ->
    Presence_Subscribe = exmpp_presence:subscribe(),
    Presence = exmpp_stanza:set_recipient(Presence_Subscribe, Recipient),
    exmpp_session:send_packet(Session, Presence).
